package gov.nist.core;

import java.util.*;

/**
 * Thread Auditor class:
 *   - Provides a mechanism for applications to check the health of internal threads
 *   - The mechanism is fairly simple:
 *   - Threads register with the auditor at startup and "ping" the auditor every so often.
 *   - The application queries the auditor about the health of the system periodically. The
 *     auditor reports if the threads are healthy or if any of them failed to ping and are
 *     considered dead or stuck.
 *   - The main implication for the monitored threads is that they can no longer block
 *     waiting for an event forever. Any wait() must be implemented with a timeout so that
 *     the thread can periodically ping the auditor.
 *
 * This code is in the public domain.
 *
 * @author R. Borba (Natural Convergence)   <br/>
 * @version 1.2
 */

public class ThreadAuditor {
	/// Threads being monitored
	private Map<Thread,ThreadHandle> threadHandles = new HashMap<Thread,ThreadHandle>();

	/// How often are threads supposed to ping
	private long pingIntervalInMillisecs = 0;

	/// Internal class, used as a handle by the monitored threads
	public class ThreadHandle {
		/// Set to true when the thread pings, periodically reset to false by the auditor
		private boolean isThreadActive;

		/// Thread being monitored
		private Thread thread;

		/// Thread auditor monitoring this thread
		private ThreadAuditor threadAuditor;

		/// Constructor
		public ThreadHandle(ThreadAuditor aThreadAuditor) {
			isThreadActive = false;
			thread = Thread.currentThread();
			threadAuditor = aThreadAuditor;
		}

		/// Called by the auditor thread to check the ping status of the thread
		public boolean isThreadActive() {
			return isThreadActive;
		}

		/// Called by the auditor thread to reset the ping status of the thread
		protected void setThreadActive(boolean value) {
			isThreadActive = value;
		}

		/// Return the thread being monitored
		public Thread getThread() {
			return thread;
		}

		// Helper function to allow threads to ping using this handle
		public void ping() {
			threadAuditor.ping(this);
		}

		// Helper function to allow threads to get the ping interval directly from this handle
		public long getPingIntervalInMillisecs() {
			return threadAuditor.getPingIntervalInMillisecs();
		}

		/**
		 * Returns a string representation of the object
		 *
		 * @return a string representation of the object
		 */
		public String toString() {
			StringBuffer toString = new StringBuffer()
					.append("Thread Name: ").append(thread.getName())
					.append(", Alive: ").append(thread.isAlive());
			return toString.toString();
		}
	}

	/// Indicates how often monitored threads are supposed to ping (0 = no thread monitoring)
	public long getPingIntervalInMillisecs() {
		return pingIntervalInMillisecs;
	}

	/// Defines how often monitored threads are supposed to ping
	public void setPingIntervalInMillisecs(long value) {
		pingIntervalInMillisecs = value;
	}

	/// Indicates if the auditing of threads is enabled
	public boolean isEnabled() {
		return (pingIntervalInMillisecs > 0);
	}

	/// Called by a thread that wants to be monitored
	public synchronized ThreadHandle addCurrentThread() {
		// Create and return a thread handle but only add it
		// to the list of monitored threads if the auditor is enabled
		ThreadHandle threadHandle = new ThreadHandle(this);
		if (isEnabled()) {
			threadHandles.put(Thread.currentThread(), threadHandle);
		}
		return threadHandle;
	}

	/// Stops monitoring a given thread
	public synchronized void removeThread(Thread thread) {
		threadHandles.remove(thread);
	}

	/// Called by a monitored thread reporting that it's alive and well
	public synchronized void ping(ThreadHandle threadHandle) {
		threadHandle.setThreadActive(true);
	}

	/// Resets the auditor
	public synchronized void reset() {
		threadHandles.clear();
	}

	/**
	 * Audits the sanity of all threads
	 *
	 * @return An audit report string (multiple lines), or null if all is well
	 */
	public synchronized String auditThreads() {
		String auditReport = null;
		// Map stackTraces = null;

		// Scan all monitored threads looking for non-responsive ones
		Iterator<ThreadHandle> it = threadHandles.values().iterator();
		while (it.hasNext()) {
			ThreadHandle threadHandle = (ThreadHandle) it.next();
			if (!threadHandle.isThreadActive()) {
				// Get the non-responsive thread
				Thread thread = threadHandle.getThread();

				// Update the audit report
				if (auditReport == null) {
					auditReport = "Thread Auditor Report:\n";
				}
				auditReport += "   Thread [" + thread.getName() + "] has failed to respond to an audit request.\n";

				/*
				 * Stack traces are not available with JDK 1.4.
				 * Feel free to uncomment this block to get a better report if you're using JDK 1.5.
				 */
				//	// Get stack traces for all live threads (do this only once per audit)
				//	if (stackTraces == null) {
				//		stackTraces = Thread.getAllStackTraces();
				//	}
				//
				//	// Get the stack trace for the non-responsive thread
				//	StackTraceElement[] stackTraceElements = (StackTraceElement[])stackTraces.get(thread);
				//	if (stackTraceElements != null && stackTraceElements.length > 0) {
				//		auditReport += "      Stack trace:\n";
				//
				//		for (int i = 0; i < stackTraceElements.length ; i ++ ) {
				//			StackTraceElement stackTraceElement = stackTraceElements[i];
				//			auditReport += "         " + stackTraceElement.toString() + "\n";
				//		}
				//	} else {
				//		auditReport += "      Stack trace is not available.\n";
				//	}
			}

			// Reset the ping status of the thread
			threadHandle.setThreadActive(false);
		}
		return auditReport;
	}

	/**
	 * Returns a string representation of the object
	 *
	 * @return a string representation of the object
	 */
	public synchronized String toString() {
		String toString = "Thread Auditor - List of monitored threads:\n";
		Iterator<ThreadHandle> it = threadHandles.values().iterator();
		while ( it.hasNext()) {
			ThreadHandle threadHandle = (ThreadHandle)it.next();
			toString += "   " + threadHandle.toString() + "\n";
		}
		return toString;
	}
}
